#ifndef _SEQUENCES_H_
#define _SEQUENCES_H_

#include <vector>
#include <sstream>
#include <fstream>
#include <pthread.h>
#include <limits.h>

#include "util.h"
#include "interpret.h"
#include "objects.h"
#include "suffixindex.h"
#include "dataset.h"
#include "intsequences.h"

void InitProblem();

struct EconomyThread;

/*
  A problem is a number of single variable functions to be learned.

  Each function's domain is divided into 3 kinds of arguments:
  - Training arguments: The value of the function on these arguments
    is known, and can be used in training.
  - Test arguments: The value of the function on these arguments is
    known as well, but these values cannot be used in training. However,
    the Test arguments themselves can be queried in training, and a
    learning algorithm is allowed to know the arguments' identity.
  - Prediction arguments: The arguments are known, but the value
    of the function on these arguments is not known by the user,
    and we seek to predict the value of the function on these arguments.

  - As a practical restriction, none of the values of the functions on
    arguments can be NIL
*/


class Problem {
 public:
  virtual int64 NumFunctions() = 0;
  virtual Thing NthFunction(int64 n) = 0;

  // returns 0 if passed a function it doesn't define. 
  virtual int64 NumArgs(Thing fname, Thing arg_type) = 0;

  // What happens in failure is problem specific
  virtual Thing NthArg(Thing fname, int64 n, Thing arg_type) = 0;

  // returns TRAINING_ARGUMENT, TEST_ARGUMENT, PREDICTION_ARGUMENT or NIL
  virtual Thing ArgType(Thing fname, Thing arg) = 0;

  // Helper function to look for arity 1
  virtual Thing ArgsType(Thing fname, Thing args);

  // description of a function, for human visibility.
  virtual Thing Description
    (EconomyThread *et, Thing fname) { return NIL; }

  // returns NULL if this result is not defined by problem.
  virtual Thing Result(Thing fname, Thing arg) = 0;

  // returns an arbitrary plausible value
  // ignored for arguments not in test / prediction domain
  virtual Thing DefaultResult(Thing fname, Thing arg) = 0;

  // Returns the correct values for Training args,
  // and default values for test and prediction args
  // Null otherwise
  Thing ImplicitResult(Thing fname, Thing arg);

  // returns NOTIMPLEMENTED if it can't handle it. 
  Thing SystemCall(EconomyThread *et,
		   Syscall which,
		   int32 num_parameters);

  // For extensions by subtypes
  virtual Thing ExtensionSystemCall(EconomyThread *et, 
				    Syscall which,
				    int32 num_parameters) {
    return NOTIMPLEMENTED; }
  
  virtual ~Problem();
};

struct IntSequenceProblem : public Problem {
  IntSequenceProblem();

  // Required calls
  int64 NumFunctions();
  Thing NthFunction(int64 n);
  int64 NumArgs(Thing fname, Thing arg_type);
  Thing NthArg(Thing fname, int64 n, Thing arg_type);
  Thing ArgType(Thing fname, Thing arg);
  Thing Description(EconomyThread * et, Thing param0);
  Thing Result(Thing fname, Thing arg);
  Thing DefaultResult(Thing fname, Thing arg);

  // helpers
  int64 GetID(Thing fname);
  int64 NumTrainingArgs(int64 id);

  vector<int64> most_common_value_;
  SequenceIndex si_;
};


struct ExplicitProblem : public Problem {
  ExplicitProblem(string filenames); // comma separated filenames. 
  // tuple consists of (fname arg arg_type result default_result)
  void AddTuple(Thing tuple);
  void LoadFromFile(string fn);

  int64 NumFunctions();
  Thing NthFunction(int64 n);
  int64 NumArgs(Thing fname, Thing arg_type);
  Thing NthArg(Thing fname, int64 n, Thing arg_type);
  Thing ArgType(Thing fname, Thing arg);
  Thing Description(EconomyThread * et, Thing param0);
  Thing Result(Thing fname, Thing arg);
  Thing DefaultResult(Thing fname, Thing arg);

  // hash_map<uint64, int64, SimpleHash<uint64> > function_id_;
  vector<Thing> fnames_;
  hash_set<uint64, SimpleHash<uint64> > fnames_set_;

  // every (fname arg) is in here twice, once under the type and once 
  // under ALL_ARGUMENTS
  hash_map<uint64, vector<Thing> , SimpleHash<uint64> > fname_type_to_args_;

  hash_map<uint64, Thing, SimpleHash<uint64> > fname_arg_to_tuple_;

  hash_map<uint64, Thing, SimpleHash<uint64> > fname_to_description_;
};

/*
  SequenceProblem System calls

  --- lexicon --- 
  TOKENIZE - string to token array. 
  LEXICONSIZE - number of tokens in lexicon

  --- corpus --- 
  TODO SEARCH - token array to (start length) range of suffix ids
  TODO SUFFIX - suffix id to encoded position
  DATASIZE - size of encoded data.
*/

struct SequenceProblem : public Problem {
  SequenceProblem();

  int64 NumFunctions() { return 1; }
  Thing NthFunction(int64 n);
  int64 NumArgs(Thing fname, Thing arg_type);
  Thing NthArg(Thing fname, int64 n, Thing arg_type);
  Thing ArgType(Thing fname, Thing arg);
  Thing Result(Thing fname, Thing arg);
  Thing DefaultResult(Thing fname, Thing arg) { return NIL; }

  Thing ExtensionSystemCall(EconomyThread *et, 
			    Syscall which, 
			    int32 num_parameters);

  // System call implementations
  Thing SearchCall (EconomyThread * et, Thing param);
  Thing SuffixCall(Thing param);

  /*
  Thing TokenizeCall(EconomyThread * et, Thing param);
  Thing LexiconSizeCall(Thing param);
  */

  // DataSet accessors
  DataSet * DS() { return ds_; }
  Corpus * GetCorpus() { return &(ds_->corpus_); }
  Lexicon * Lex() { return &(ds_->lexicon_); }

  // Dealing with the DL of tokens
  uint64 TokenCount(uint64 token_id);
  int64 TokenLikelihood(uint64 token_id);

  DataSet * ds_;
};

#endif
